/*
 * Canvas.h
 *
 *	Author: stravant
 *	Description:
 *		
 */

#ifndef CANVAS_H_
#define CANVAS_H_

#include <iostream>
#include <vector>
#include "cairo/cairo.h"
#include "../core/Vec.h"
#include "../core/Rect.h"
#include "../core/Bits.h"

namespace tbt {

namespace fx {

class Context;
class Pattern;

class Color {
public:
	Color(): R(0), G(0), B(0), A(1.0) {}
	Color(double r, double g, double b, double a = 1.0): R(r), G(g), B(b), A(a) {}
	Color(const Color& other): R(other.R), G(other.G), B(other.B), A(other.A) {}

	static Color FromHSL(double h, double s, double l, double a = 1.0) {
		Color c;
		c.setHSL(h, s, l, a);
		return c;
	}

	Color& set(double r, double g, double b, double a = 1.0) {
		R = r; G = g; B = b; A = a;
		return *this;
	}

	Color& setHSL(double h, double s, double l, double a = 1.0);

	bool operator==(const Color& other) const {
		return R == other.R && G == other.G &&
				B == other.B && A == other.A;
	}

	double r() const {return R; }
	double g() const {return G; }
	double b() const {return B; }
	double a() const {return A; }
	void toHSL(double& h, double& s, double& v) const;

	void lighten(double amount = 0.1);
	void darken(double amount = 0.1);
	void invert();

	Color darker(double amount = 0.1) const;
	Color lighter(double amount = 0.1) const;
	Color inverse() const;

	static const Color Red;
	static const Color Green;
	static const Color Blue;
	static const Color Cyan;
	static const Color Magenta;
	static const Color Yellow;
	static const Color Black;
	static const Color White;
private:
	double R, G, B, A;
};

std::ostream& operator<<(std::ostream& out, const Color& c);

class Surface {
public:
	Surface(cairo_surface_t* surface): mSurface(surface) {}
	~Surface() {cairo_surface_destroy(mSurface); }

private:
	friend class Context;
	friend class Pattern;
	cairo_surface_t* mSurface;
};

class Pattern {
public:
	Pattern(cairo_pattern_t* pattern): mPattern(pattern) {
		//cairo_pattern_reference(mPattern);
	}
	Pattern(cairo_pattern_t* pattern, Surface* surf): mPattern(pattern), mSurface(surf) {
		//cairo_pattern_reference(mPattern);
	}
	~Pattern() {cairo_pattern_destroy(mPattern); }

	static Pattern* CreateColor(const Color& c)
		{return new Pattern(cairo_pattern_create_rgba(c.r(), c.g(), c.b(), c.a())); }
	static Pattern* CreateForSurface(Surface* surf)
		{return new Pattern(cairo_pattern_create_for_surface(surf->mSurface), surf); }
	static Pattern* CreateLinear(double x0, double y0, double x1, double y1)
		{return new Pattern(cairo_pattern_create_linear(x0, y0, x1, y1)); }

	void addColorStop(double offset, const Color& color)
		{cairo_pattern_add_color_stop_rgba(mPattern, offset, color.r(), color.g(), color.b(), color.a()); }
	int getColorStopCount() {
		int n;
		cairo_pattern_get_color_stop_count(mPattern, &n);
		return n;
	}
	Color getColor() {
		double r, g, b, a;
		cairo_pattern_get_rgba(mPattern, &r, &g, &b, &a);
		return Color(r, g, b, a);
	}
	Surface* getSurface()
		{return mSurface; }


private:
	friend class Context;
	cairo_pattern_t* mPattern;
	Surface* mSurface;
};

class Path {
public:
	Path(cairo_path_t* path): mPath(path) {}
	~Path() {cairo_path_destroy(mPath); }

private:
	friend class Context;
	cairo_path_t* mPath;
};

class Context {
public:
	enum AntiAlias {
		kAADefault = 	CAIRO_ANTIALIAS_DEFAULT,
		kAANone = 		CAIRO_ANTIALIAS_NONE,
		kAAGray = 		CAIRO_ANTIALIAS_GRAY,
		kAASubpixel = 	CAIRO_ANTIALIAS_SUBPIXEL,
	};

	enum FillRule {
		kFillWinding = CAIRO_FILL_RULE_WINDING,
		kFillEvenOdd = CAIRO_FILL_RULE_EVEN_ODD,
	};

	enum LineCap {
	    kLineCapButt =	CAIRO_LINE_CAP_BUTT,
	    kLineCapRound =	CAIRO_LINE_CAP_ROUND,
	    kLineCapSquare =CAIRO_LINE_CAP_SQUARE
	};

	enum LineJoin {
	    kLineJoinMiter = CAIRO_LINE_JOIN_MITER,
	    kLineJoinRound = CAIRO_LINE_JOIN_ROUND,
	    kLineJoinBevel = CAIRO_LINE_JOIN_BEVEL
	};

	enum Operator {
	    kOpClear = CAIRO_OPERATOR_CLEAR,

	    kOpSource = CAIRO_OPERATOR_SOURCE,
	    kOpOver = CAIRO_OPERATOR_OVER,
	    kOpIn = CAIRO_OPERATOR_IN,
	    kOpOut = CAIRO_OPERATOR_OUT,
	    kOpAtop = CAIRO_OPERATOR_ATOP,

	    kOpDest = CAIRO_OPERATOR_DEST,
	    kOpDestOver = CAIRO_OPERATOR_DEST_OVER,
	    kOpDestIn = CAIRO_OPERATOR_DEST_IN,
	    kOpDestOut = CAIRO_OPERATOR_DEST_OUT,
	    kOpDestAtop = CAIRO_OPERATOR_DEST_ATOP,

	    kOpXor = CAIRO_OPERATOR_XOR,
	    kOpAdd = CAIRO_OPERATOR_ADD,
	    kOpSaturate = CAIRO_OPERATOR_SATURATE,

	    kOpMultiply = CAIRO_OPERATOR_MULTIPLY,
	    kOpScreen = CAIRO_OPERATOR_SCREEN,
	    kOpOverlay = CAIRO_OPERATOR_OVERLAY,
	    kOpDarken = CAIRO_OPERATOR_DARKEN,
	    kOpLighten = CAIRO_OPERATOR_LIGHTEN,
	    kOpColorDodge = CAIRO_OPERATOR_COLOR_DODGE,
	    kOpColorBurn = CAIRO_OPERATOR_COLOR_BURN,
	    kOpHardLight = CAIRO_OPERATOR_HARD_LIGHT,
	    kOpSoftLight = CAIRO_OPERATOR_SOFT_LIGHT,
	    kOpDifference = CAIRO_OPERATOR_DIFFERENCE,
	    kOpExclusion = CAIRO_OPERATOR_EXCLUSION,
	    kOpHslHue = CAIRO_OPERATOR_HSL_HUE,
	    kOpHslSaturation = CAIRO_OPERATOR_HSL_SATURATION,
	    kOpHslColor = CAIRO_OPERATOR_HSL_COLOR,
	    kOpHslLuminosity = CAIRO_OPERATOR_HSL_LUMINOSITY
	};

	enum PatternType {
	    kPatternSolid = CAIRO_PATTERN_TYPE_SOLID,
	    kPatternSurface = CAIRO_PATTERN_TYPE_SURFACE,
	    kPatternLinear = CAIRO_PATTERN_TYPE_LINEAR,
	    kPatternRadial = CAIRO_PATTERN_TYPE_RADIAL
	};

public:
	Context(cairo_t* cr): mContext(cr) {}
	~Context() {cairo_destroy(mContext); }

	//temporary, get the context while some of the stuff is still
	//to be implemented in the class
	cairo_t* getInternal() {return mContext; }

	//save restore states
	void save() {cairo_save(mContext); }
	void restore() {cairo_restore(mContext); }

	//set csources
	void setSourceColor(double r, double g, double b)
		{cairo_set_source_rgb(mContext, r, g, b); }
	void setSourceColor(double r, double g, double b, double a)
		{cairo_set_source_rgba(mContext, r, g, b, a); }
	void setSourceColor(const Color& color)
		{setSourceColor(color.r(), color.g(), color.b(), color.a()); }
	void setSourceSurface(Surface* surface, const DVec& v)
		{cairo_set_source_surface(mContext, surface->mSurface, v.x(), v.y()); }
	void setSource(Pattern* pattern)
		{cairo_set_source(mContext, pattern->mPattern); }

	//antialiasing
	AntiAlias getAntiAlias()
		{return static_cast<AntiAlias>(cairo_get_antialias(mContext)); }
	void setAntiAlias(AntiAlias a)
		{cairo_set_antialias(mContext, static_cast<cairo_antialias_t>(a)); }

	//dashes
	void getDash(std::vector<double>& dashes, double& offset) {
		auto nDashes = cairo_get_dash_count(mContext);
		dashes.resize(nDashes);
		cairo_get_dash(mContext, dashes.data(), &offset);
	}
	void setDash(const std::vector<double>& dashes, double offset)
		{cairo_set_dash(mContext, dashes.data(), dashes.size(), offset); }

	//fill rule
	void setFillRule(FillRule f)
		{cairo_set_fill_rule(mContext, static_cast<cairo_fill_rule_t>(f)); }
	FillRule getFillRule()
		{return static_cast<FillRule>(cairo_get_fill_rule(mContext)); }

	//line caps
	void setLineCap(LineCap cap)
		{cairo_set_line_cap(mContext, static_cast<cairo_line_cap_t>(cap)); }
	LineCap getLineCap()
		{return static_cast<LineCap>(cairo_get_line_cap(mContext)); }

	//line join
	void setLineJoin(LineJoin join)
		{cairo_set_line_join(mContext, static_cast<cairo_line_join_t>(join)); }
	LineJoin getLineJoin()
		{return static_cast<LineJoin>(cairo_get_line_join(mContext)); }

	//line width
	void setLineWidth(double w)
		{cairo_set_line_width(mContext, w); }
	double getLineWidth()
		{return cairo_get_line_width(mContext); }

	//miter limit
	void setMiterLimit(double lim)
		{cairo_set_miter_limit(mContext, lim); }
	double getMiterLimit()
		{return cairo_get_miter_limit(mContext); }

	//ops
	void setOperator(Operator op)
		{cairo_set_operator(mContext, static_cast<cairo_operator_t>(op)); }
	Operator getOperator()
		{return static_cast<Operator>(cairo_get_operator(mContext)); }

	//tolerance
	void setTolerance(double n)
		{cairo_set_tolerance(mContext, n); }
	double getTolterance()
		{return cairo_get_tolerance(mContext); }

	//clip
	void clip()
		{cairo_clip(mContext); }
	void clipPreserve()
		{cairo_clip_preserve(mContext); }
	void clipExtents(DRect& out) {
		double x1, y1, x2, y2;
		cairo_clip_extents(mContext, &x1, &y1, &x2, &y2);
		out.set(x1, y1, x2-x1, y2-y1);
	}
	bool inClip(const DVec& p)
		{return (cairo_in_clip(mContext, p.x(), p.y())) ? true : false; }

	//fill
	void fill()
		{cairo_fill(mContext); }
	void fillPreserve()
		{cairo_fill_preserve(mContext); }
	void fillExtents(DRect& out) {
		double x1, y1, x2, y2;
		cairo_fill_extents(mContext, &x1, &y1, &x2, &y2);
		out.set(x1, y1, x2-x1, y2-y1);
	}
	bool inFill(const DVec& p)
		{return (cairo_in_fill(mContext, p.x(), p.y())) ? true : false; }

	//mask
	void mask(Pattern* pattern)
		{cairo_mask(mContext, pattern->mPattern); }
	void maskSurface(Surface* surface, const DVec& p)
		{cairo_mask_surface(mContext, surface->mSurface, p.x(), p.y()); }

	//paint
	void paint()
	{cairo_paint(mContext); }
	void paintWithAlpha(double alpha)
	{cairo_paint_with_alpha(mContext, alpha); }

	//stroke
	void stroke()
		{cairo_stroke(mContext); }
	void strokePreserve()
		{cairo_stroke_preserve(mContext); }
	void strokeExtents(DRect& out) {
		double x1, y1, x2, y2;
		cairo_stroke_extents(mContext, &x1, &y1, &x2, &y2);
		out.set(x1, y1, x2-x1, y2-y1);
	}
	bool inStroke(const DVec& p)
		{return (cairo_in_stroke(mContext, p.x(), p.y())) ? true : false; }

	//page
	void copyPage()
		{cairo_copy_page(mContext); }
	void showPage()
		{cairo_show_page(mContext); }

	//path drawing
	Path* copyPath()
		{return new Path(cairo_copy_path(mContext)); }
	Path* copyPathFlat()
		{return new Path(cairo_copy_path_flat(mContext)); }
	void appendPath(const Path* path)
		{cairo_append_path(mContext, path->mPath); }

	bool hasCurrentPoint()
		{return cairo_has_current_point(mContext) ? true : false; }
	DVec getCurrentPoint() {
		double x, y;
		cairo_get_current_point(mContext, &x, &y);
		return DVec(x, y);
	}
	void newPath()
		{cairo_new_path(mContext); }
	void newSubPath()
		{cairo_new_sub_path(mContext); }
	void closePath()
		{cairo_close_path(mContext); }
	void arc(const DVec& p, double radius, double O1, double O2)
		{cairo_arc(mContext, p.x(), p.y(), radius, O1, O2); }
	void arc(double px, double py, double radius, double O1, double O2)
		{cairo_arc(mContext, px, py, radius, O1, O2); }
	void arcNegative(const DVec& p, double radius, double O1, double O2)
		{cairo_arc_negative(mContext, p.x(), p.y(), radius, O1, O2); }
	void arcNegative(double px, double py, double radius, double O1, double O2)
		{cairo_arc_negative(mContext, px, py, radius, O1, O2); }
	void curveTo(const DVec& a, const DVec& b, const DVec& c)
		{cairo_curve_to(mContext, a.x(), a.y(), b.x(), b.y(), c.x(), c.y()); }
	void curveTo(double ax, double ay, double bx, double by, double cx, double cy)
		{cairo_curve_to(mContext, ax, ay, bx, by, cx, cy); }
	void lineTo(const DVec& p)
		{cairo_line_to(mContext, p.x(), p.y()); }
	void lineTo(double px, double py)
		{cairo_line_to(mContext, px, py); }
	void moveTo(const DVec& p)
		{cairo_move_to(mContext, p.x(), p.y()); }
	void moveTo(double px, double py)
		{cairo_move_to(mContext, px, py); }
	void rectangle(const DRect& rect)
		{cairo_rectangle(mContext, rect.x(), rect.y(), rect.width(), rect.height()); }
	void rectangle(double x, double y, double w, double h)
		{cairo_rectangle(mContext, x, y, w, h); }
	void relCurveTo(const DVec& a, const DVec& b, const DVec& c)
		{cairo_rel_curve_to(mContext, a.x(), a.y(), b.x(), b.y(), c.x(), c.y()); }
	void relLineTo(const DVec& p)
		{cairo_rel_line_to(mContext, p.x(), p.y()); }
	void relLineTo(double px, double py)
		{cairo_rel_line_to(mContext, px, py); }
	void relMoveTo(const DVec& p)
		{cairo_rel_move_to(mContext, p.x(), p.y()); }
	void relMoveTo(double px, double py)
		{cairo_rel_move_to(mContext, px, py); }
	void pathExtents(DRect& out) {
		double x1, y1, x2, y2;
		cairo_path_extents(mContext, &x1, &y1, &x2, &y2);
		out.set(x1, y1, x2-x1, y2-y1);
	}

	//extras
	void roundedRectangle(const DRect& r, double radius) {
		newPath();
		double c = 0.55228475 * radius;
		double x = r.x(), y = r.y(), w = r.width(), h = r.height();
		moveTo(DVec(x+radius,y));
		lineTo(DVec(x+w-radius,y));
		curveTo(DVec(x+w-radius+c,y), DVec(x+w,y+radius-c), DVec(x+w,y+radius));
		lineTo(DVec(x+w,y+h-radius));
		curveTo(DVec(x+w,y+h-radius+c), DVec(x+w-radius+c,y+h), DVec(x+w-radius,y+h));
		lineTo(DVec(x+radius,y+h));
		curveTo(DVec(x+radius-c,y+h), DVec(x,y+h-radius+c), DVec(x,y+h-radius));
		lineTo(DVec(x,y+radius));
		curveTo(DVec(x,y+radius-c), DVec(x+radius-c,y), DVec(x+radius,y));
		closePath();
	}
private:
	cairo_t* mContext;
};

}

}

#endif /* CANVAS_H_ */





















